/*

   **********************************************************************
   *                                                                    *
   * This file contains the code for an image object for a content-     *
   * based image retrieval application that "summarizes" an image and   *
   * sends the resulting image "signature" to a server for              *
   * classification. The server returns the classification result in    *
   * the form of a Web page containing example images of the input      *
   * image's category.                                                  *
   *                                                                    *
   * The image class defined here is rather specific to this particular *
   * application and is not really designed for broad reuse (just yet). *
   *                                                                    *
   **********************************************************************
*/

/*
   **********************************************************************
   *                                                                    *
   * Gist   -- content-based image retrieval application for the gPhone *
   *                                                                    *
   * Copyright (C) 2011 Yong Zeng                                       *
   *                                                                    *
   * This file is part of Gist.                                       *
   *                                                                    *
   * Gist is free software; you can redistribute it and/or modify it  *
   * under the terms of the GNU General Public License as published by  *
   * the Free Software Foundation; either version 2 of the License, or  *
   * (at your option) any later version.                                *
   *                                                                    *
   * Gist is distributed in the hope that it will be useful, but      *
   * WITHOUT ANY WARRANTY; without even the implied warranty of         *
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU   *
   * General Public License for more details.                           *
   *                                                                    *
   * You should have received a copy of the GNU General Public License  *
   * along with Goggle; if not, write to the Free Software Foundation,  *
   * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.       *
   *                                                                    *
   **********************************************************************
*/
//----------------------- PACKAGE SPECIFICATION -------------------------

package vision.gistcomputing ;

//------------------------------ IMPORTS --------------------------------

// Android UI support
import android.widget.SimpleCursorAdapter ;
import android.widget.ImageView ;
import android.widget.GridView ;
import android.widget.AdapterView ;

import android.view.Menu ;
import android.view.MenuItem ;
import android.view.View ;

// Android content-provider support
import android.provider.MediaStore.Images ;

// Android database support
import android.database.Cursor ;

// Android application and OS support
import android.content.Context ;
import android.app.Activity ;
import android.content.SharedPreferences ;
import android.content.Intent ;
import android.os.Bundle ;
import android.os.Handler ;
import android.os.Message ;

// Android utilities
import android.content.ContentUris ;
import android.net.Uri ;
import android.util.Log ;

// Java I/O
import java.io.File ;

// Java thread support
import java.lang.Thread ;

import vision.gistcomputing.R;

//--------------------- APPLICATION'S MAIN SCREEN -----------------------

/**
   This class implements the main screen of a content-based image
   retrieval application. This screen displays the thumbnails of all the
   currently available images on the phone. Users can select one of these
   images to have the application return a list of matching images.
   Additionally, users also have the option to capture a new image with
   the gPhone's built-in camera and retrieve images matching this newly
   captured image.
*/
public class MainActivity extends Activity {

// The main screen of the content-based image retrieval application shows
// thumbnails of all the available images in a neat grid.
GridView m_thumbnails_grid ;

/// Since importing images from the application's files area into the
/// Android MediaStore database can be quite CPU-intensive, we do it in a
/// separate thread. This worker thread sends the main thread a message
/// when it is done so that the thumbnails grid can be refreshed with the
/// newly available images once they're available.
///
/// These codes serve as the IDs of the messages exchanged between the
/// main and worker threads.
///@{
static final int IMPORT_DONE_MSG = 1 ;
///@}

/// This handler object is the one used by the image import worker thread
/// to send various notifications to the main thread.
Handler m_status_handler = new Handler() {
   public void handleMessage(Message M) {
      switch (M.what) {
         case IMPORT_DONE_MSG:
            display_thumbnails(m_thumbnails_grid) ;
            break ;
      }
   }
} ;

//-------------------------- INITIALIZATION -----------------------------

/// This method is called when the activity is first created. It is akin
/// to the "main" function in normal desktop applications.
@Override public void onCreate(Bundle saved_state)
{
   super.onCreate(saved_state) ;
   setContentView(R.layout.main) ;

   // Sync the internal Settings object with Android's persistent shared
   // preferences cache.
   sync_settings() ;

   // Setup the thumbnails grid
   final Context context = this ; // for access inside anonymous click listener
   m_thumbnails_grid = (GridView) findViewById(R.id.thumbnails_grid) ;
   m_thumbnails_grid.setOnItemClickListener(
      new AdapterView.OnItemClickListener() {
         public void onItemClick(AdapterView A, View V, int pos, long id) {
            GistActivity.start(context, id) ;
         }}) ;

   // Display the thumbnails of all available images
   display_thumbnails(m_thumbnails_grid) ;
}

// When the application starts-up, the internal Settings object has a
// default state that may differ from the actual settings the user saved
// last time around when s/he used Goggle. We need to bring these two
// things in sync...
private void sync_settings()
{
   SharedPreferences prefs = getSharedPreferences(Settings.NAME, 0) ;
   Settings.gist_estimator(
      prefs.getString(Settings.GIST_ESTIMATOR, Settings.gist_estimator())) ;
   Settings.server_address(
      prefs.getString(Settings.SERVER_ADDRESS, Settings.server_address())) ;
   Settings.upload_policy(
      prefs.getInt(Settings.UPLOAD_POLICY, Settings.upload_policy())) ;
   Settings.vocabulary_file(getFilesDir().getPath() + File.separator +
                            getString(R.string.vocabulary_file_name)) ;
}

/// This method is called the first time the application's menu is
/// displayed. The code to setup the menu is pretty much boilerplate,
/// i.e., inflate the appropriate menu from an XML resource file.
@Override public boolean onCreateOptionsMenu(Menu menu)
{
   getMenuInflater().inflate(R.menu.main_menu, menu) ;
   return true ;
}

//--------------------------- MENU COMMANDS -----------------------------

/// This method is called when a menu item from the activity's menu is
/// selected. The handler is pretty much boilerplate; it simply
/// despatches handling for each menu item to an appropriate method.
@Override public boolean onOptionsItemSelected(MenuItem item)
{
   switch (item.getItemId())
   {
      case R.id.add_picture_menu_cmd:
         ImageRecorder.start(this) ;
         return true ;

      case R.id.import_menu_cmd:
         new ImportThread(this, m_status_handler) ;
         return true ;

      case R.id.remove_picture_menu_cmd:
         remove_image(m_thumbnails_grid.getSelectedItemId()) ;
         return true ;

      case R.id.remove_all_menu_cmd:
         Utils.delete_all_pictures(this) ;
         display_thumbnails(m_thumbnails_grid) ;
         return true ;

      case R.id.settings_menu_cmd:
         SettingsActivity.start(this) ;
         return true ;

      case R.id.test_menu_cmd:
         TestService.start(this) ;
         return true ;
   }
   return super.onOptionsItemSelected(item) ;
}

// Remove the image corresponding to the selected thumbnail
private void remove_image(long thumbnail_id)
{
   if (thumbnail_id == m_thumbnails_grid.INVALID_ROW_ID) {
      Utils.notify(this, getString(R.string.select_thumbnail_msg)) ;
      return ;
   }

   long image_id  = Utils.full_picture_id(this, thumbnail_id) ;
   Utils.delete_picture(this, image_id) ;
   display_thumbnails(m_thumbnails_grid) ;
}

//------------------------- IMAGE THUMBNAILS ----------------------------

// Display the available thumbnails in the specified grid view
void display_thumbnails(GridView G)
{
   String[] from = new String[] {Images.Thumbnails._ID} ;
   int[]    to   = new int[]    {R.id.thumbnail} ;
   G.setAdapter(new ThumbnailsAdapter(this, R.layout.thumbnail,
                                      get_thumbnails(), from, to)) ;
}

// Retrieve the available thumbnail IDs
private Cursor get_thumbnails()
{
   try
   {
      String[] columns = new String[] {
         Images.Thumbnails._ID,
      } ;
      String where_clause =
         Images.Thumbnails.KIND + "=" + Images.Thumbnails.MINI_KIND ;
      return managedQuery(Images.Thumbnails.EXTERNAL_CONTENT_URI, columns,
                          where_clause, null, null) ;
   }
   catch (android.database.sqlite.SQLiteException e)
   {
      Log.e(null, "MVN: unable to retrieve thumbnails", e) ;
      return null ;
   }
}

//------------------------ THUMBNAILS ADAPTER ---------------------------

/**
   This inner class connects the thumbnails obtained from the Android
   MediaStore to the grid view used to display the thumbnails on the main
   screen of the photo manager application.
*/
private class ThumbnailsAdapter extends SimpleCursorAdapter {

/// Our custom adapter's constructor takes the same arguments as a
/// standard SimpleCursorAdapter. The only difference between this
/// adapter and the SimpleCursorAdapter is that it uses a custom view
/// binder to bind cursor columns to corresponding UI elements.
public ThumbnailsAdapter(Context context, int layout, Cursor cursor,
                         String[] from, int[] to)
{
   super(context, layout, cursor, from, to) ;
   setViewBinder(new ViewBinder()) ;
}

/// This inner class is the binder used to bind thumbnail IDs to an image
/// view.
private class ViewBinder implements SimpleCursorAdapter.ViewBinder {

/// This method is called by SimpleCursorAdapter for "transferring" the
/// contents of each underlying data item (as pointed to by its cursor)
/// to an appropriate UI element.
///
/// In our case, the cursor holds the IDs of all the available thumbnails
/// on the phone. We want to draw these thumbnails on image views that
/// are arranged in a grid. To do this, we create the appropriate URI for
/// the thumbnail using its ID and instruct the target image view to
/// obtain its contents through that URI.
public boolean setViewValue(View V, Cursor C, int column)
{
   int i = C.getColumnIndex(Images.Thumbnails._ID) ;
   if (column == i) {
      ImageView img = (ImageView) V ;
      img.setImageURI(ContentUris.withAppendedId(
         Images.Thumbnails.EXTERNAL_CONTENT_URI, C.getInt(i))) ;
      return true ;
   }
   return false ;
}

} // end of inner class MainActivity.ThumbnailsAdapter.ViewBinder
} // end of inner class MainActivity.ThumbnailsAdapter

//------------------------ IMAGE IMPORT THREAD --------------------------

/**
   Importing lots of images from the application's files area to the
   Android MediaStore database can be quite time consuming. To avoiding
   Android's dreaded "Application not responding" message, we perform the
   import operation in a separate worker thread.
*/
private class ImportThread extends Thread {

/// To load images from the application's files area and put them into
/// the Android MediaStore.Images database and for other such tasks, the
/// worker thread will need a valid Android context.
private Context m_context ;

/// When it's done, this worker thread will inform the main one with a
/// message, which the main thread can then use to refresh the thumbnails
/// grid. This message is relayed to the main thread via a suitable
/// Handler object passed to the worker thread by the main activity.
private Handler m_status_handler ;

/// When the worker thread object is created, we immediately start it
/// running so that the main activity (this class's client) only needs to
/// instantitate it and not have to go through any extensive set-up
/// procedures.
public ImportThread(Context C, Handler H)
{
   super("MainActivity.ImportThread") ;

   m_context = C ;
   m_status_handler = H ;

   start() ;
}

/// This is the "main" method for the import worker thread. It performs
/// the import operation, which involves "scanning" the files area for
/// .jpg files, loading and storing them in the database, and, finally,
/// removing the pushed files so as to obviate the need for a manual
/// clean-up.
public void run()
{
   final Context context = m_context ;
   Utils.Function import_it = new Utils.Function() {
         public Object apply(Object file_name) {
            String name = (String) file_name ;
            Utils.import_image(context, name, 640, 480) ;
            Utils.unlink(name) ;
            return null ;
         }} ;

   final String matching_pattern =
     context.getFilesDir().getPath() + File.separator + ".*\\.jpg" ;
   Utils.for_each_file(matching_pattern, import_it) ;

   Utils.notify_main_thread(m_status_handler, IMPORT_DONE_MSG) ;
}

} // end of inner class MainActivity.ImportThread

//-----------------------------------------------------------------------

} 
